package guiclient;

import guicommon.Message;
import guicommon.MessageType;
import guicommon.controls.handlers.EventMessage;
import guicommon.gui.CallBackControlMessage;
import guicommon.gui.ControlMessage;
import guicommon.gui.EmptyControlMessage;
import guicommon.gui.UpdateMessage;
import guicommon.session.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.CharBuffer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.*;

/** Реализует клиентскую часть
 */
public class Client {
    //Порт, на котором слушает сервер
    private final int serverPort = 6666;
    //Адрес сервера
    private final String serverHost = "127.0.0.1";
    //Сокет, для подключения к серверу
    private Socket mySocket;
    //Поток ввода данных от сервера
    private BufferedReader input;
    //Поток вывода данных на сервер
    private PrintWriter output;
    //Статус соединения
    private boolean isConnected;
    //Примитив синхронизации
    private Lock lockReed;
    //Примитив синхронизации
    private Lock lockWrite;

    private Application myApp;

    private boolean isStoping = false;

    //Конструктор
    public Client(Application myApp) throws IOException {
        mySocket = new Socket(serverHost, serverPort);
        input = new BufferedReader(new InputStreamReader(mySocket.getInputStream()));
        output = new PrintWriter(mySocket.getOutputStream(), true);
        isConnected = false;
        lockReed = new ReentrantLock();
        lockWrite = new ReentrantLock();
        this.myApp = myApp;
    }

    //Запускает клиента
    public void Start() {
        //Если уже подключены, выходим
        if (isConnected)
            return;
        isConnected = true;

        //Создаем и сразу запускаем клиентский поток
        new Thread(new Runnable() {

            private boolean isSendFirstMessage = false;

            @Override
            public void run() {
                CharBuffer msgBuffer = CharBuffer.allocate(Message.MAX_MESSAGE_CAPACITY);
                while (isConnected) {
                    lockWrite.lock();
                    msgBuffer.clear();
                    msgBuffer = CharBuffer.allocate(Message.MAX_MESSAGE_CAPACITY);
                    try {
                        Message msg;
                        do {
                            msg = isStoping ? new DisconnectMessage() : CreateMessage();
                            if (msg.getClass() == EmptyControlMessage.class) {
                                try {
                                    Thread.sleep(50);
                                } catch (InterruptedException ex) {
                                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                                }
                            }
                        } while (msg.getClass() == EmptyControlMessage.class);
                        System.out.printf("Sending message %s", msg.GetCharSequence().toString());
                        System.out.println();
                        output.println(msg.GetCharSequence().toString());
                        if (msg.GetMessageType() == MessageType.Session && msg.GetMessageCode() == DisconnectMessage.ID) {
                            System.out.println("Disconnected");
                            Stop();
                        }
                    } catch (Exception e) {
                        System.out.println(e.toString());
                        Stop();
                    } finally {
                        msgBuffer.clear();
                        lockWrite.unlock();
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            }

            private Message CreateMessage() {
                if (!isSendFirstMessage) {
                    isSendFirstMessage = true;
                    return new ConnectMessage();
                }
                ControlMessage controlMessage = myApp.GetNextControlMessage();
                if (controlMessage.getClass() == EmptyControlMessage.class) {
                    UpdateMessage update = myApp.GetNextUpdateMessage();
                    if (update != null) {
                        return update;
                    }
                }
                return controlMessage;
            }
            
        }).start();

        //Создаем и сразу запускаем клиентский поток
        new Thread(new Runnable() {

            private Message lastAnswer;

            @Override
            public void run() {
                CharBuffer msgBuffer = CharBuffer.allocate(Message.MAX_MESSAGE_CAPACITY);
                while (isConnected) {
                    lockReed.lock();
                    msgBuffer.clear();
                    msgBuffer = CharBuffer.allocate(Message.MAX_MESSAGE_CAPACITY);
                    try {
                        while (!input.ready()) {
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException ex) {
                                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                        
                        String line = input.readLine();
                        msgBuffer.put(line);
                        msgBuffer.position(0);                        
                        lastAnswer = Message.ParseMessage(msgBuffer);
                        System.out.printf("Received message %s", lastAnswer.GetCharSequence().toString());
                        System.out.println();
                        ProcMessage();
                    } catch (Exception e) {
                        lastAnswer = new ErrorMessage(e.toString());
                        Stop();
                    } finally {
                        msgBuffer.clear();
                        if (lastAnswer.GetMessageType() == MessageType.Session && 
                                (lastAnswer.GetMessageCode() == DisconnectMessage.ID
                                || lastAnswer.GetMessageCode() == ErrorMessage.ID)) {
                            Stop();
                        }
                        lockReed.unlock();
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            }

            private void ProcMessage() {
                if (lastAnswer.GetMessageType() == MessageType.GUI && lastAnswer.GetMessageCode() == CallBackControlMessage.ID) {
                    myApp.SetIdToLastSendedControl(lastAnswer.GetBodyField(CallBackControlMessage.RETURN_ID));
                    return;
                }
                if (lastAnswer.GetMessageType() == MessageType.Handler)
                    myApp.GetNextHandlerMessage((EventMessage) lastAnswer);
            }
        }).start();        
    }

    //Останавливает клиента
    public void Stop() {
        try {
            try {
                lockWrite.lock();
                isConnected = false;                
            } finally {
                lockWrite.unlock();
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) { }
            mySocket.close();
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, "Server stop error: ", ex);
        }
    }

    public void Close() {
        isStoping = true;
    }
}
